package com.vvv.shortlink.project.aop;

import com.vvv.shortlink.project.annotation.MQIdempotent;
import com.vvv.shortlink.project.constant.RedisKeyConstant;
import com.vvv.shortlink.project.dto.biz.LinkStatsRecordDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author vvv
 * @date 2024/2/29 10 27
 * discription
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class MQIdempotentAop {

    private final StringRedisTemplate stringRedisTemplate;

    @Around("@annotation(mqIdempotent)")
    public Object MQIdempotentHandle(ProceedingJoinPoint joinPoint, MQIdempotent mqIdempotent) throws MQClientException {
        Object arg = joinPoint.getArgs()[0];
        LinkStatsRecordDTO linkStatsRecordDTO = (LinkStatsRecordDTO) arg;
        String keys = linkStatsRecordDTO.getKeys();
        String key = RedisKeyConstant.MQ_IDEMPOTENT_KEY + keys;
        // 1. 检查消息是否已处理或业务流程是否完成
        if (isProcessedOrAccomplish(key)) {
            return null;
        }
        try {
            // 2. 处理逻辑
            joinPoint.proceed();
            // 4. 正常，标记已完成
            setAccomplish(key);
            log.info("成功消费一条消息: {}", keys);
        } catch (Throwable e) {
            // 3. 异常抛出
            throw new MQClientException("需要重新消费消息", e);
        }
        return null;
    }

    /**
     * 尝试消费
     * @param key
     * @return
     */
    private boolean isProcessedOrAccomplish(String key) {
        String value = stringRedisTemplate.opsForValue().get(key);
        // 如果value为null，表示消息未处理，尝试设置新值并处理消息
        if (value == null) {
            return !Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "0", 2, TimeUnit.MINUTES));
        }
        // 如果value为"1"，表示业务流程已完成
        return Objects.equals("1", value);
    }

    /**
     * 如果业务逻辑全部完成，那么设置幂等key的状态为已完成 1
     * @param key
     */
    private void setAccomplish(String key) {
        stringRedisTemplate.opsForValue().set(key, "1", 2, TimeUnit.MINUTES);
    }
}
